using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using Ares.CommonObjects;

namespace cb0tServer
{
    class LinkClient
    {
        public enum LinkMode : byte
        {
            Client = 0,
            Server = 1
        };

        public enum LinkError : byte
        {
            LinkToSelf = 0,
            LinkingDisabled = 3,
            OldProtocol = 4,
            NoInbound = 5,
            BannedUser = 9,
            WrongLanguage = 10,
            NotOnAllowList = 88
        };

        public String hashlink;
        public String room_name;
        public LinkMode mode;
        public Socket socket = null;
        public IPAddress target_ip;
        public ushort target_port;
        public bool Connected = false;
        public bool logged_in = false;
        public int session_spawn_time = 0;
        public int last_update_sent_time = 0;
        public int last_update_received_time = 0;
        public int zeros = 0;

        public List<byte> inbound_buffer = new List<byte>(); // for dumping received data
        private Queue<byte[]> outbound_buffer = new Queue<byte[]>(); // for queuing packets
        private List<LinkedUserObject> linked_users = new List<LinkedUserObject>();

        public LinkClient() { }

        /// <summary>
        /// we are the client
        /// </summary>
        public LinkClient(IPEndPoint address, int session_spawn_time) // we are the client
        {
            this.mode = LinkMode.Client;
            this.session_spawn_time = session_spawn_time;
            this.last_update_sent_time = session_spawn_time;
            this.last_update_received_time = session_spawn_time;
            this.socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            this.socket.Blocking = false;
            this.target_ip = address.Address;
            this.target_port = (ushort)address.Port;
            this.outbound_buffer.Enqueue(ServerOutboundPackets.RequestLinkPacket());
            this.Connected = false;
            this.logged_in = false;

            try // would block
            {
                this.socket.Connect(address);
            }
            catch { }
        }

        /// <summary>
        /// we are the server
        /// </summary>
        public LinkClient(Socket socket, int session_spawn_time) // we are the server
        {
            this.mode = LinkMode.Server;
            this.session_spawn_time = session_spawn_time;
            this.last_update_sent_time = session_spawn_time;
            this.last_update_received_time = session_spawn_time;
            this.socket = socket;
            this.socket.Blocking = false;
            this.target_ip = ((IPEndPoint)(socket.RemoteEndPoint)).Address;
            this.Connected = true;
            this.logged_in = false;
        }

        public bool IsSet()
        {
            return this.socket != null;
        }

        public bool IsLinking()
        {
            return this.socket != null && this.logged_in;
        }

        public void SetRoomInfo()
        {
            ChannelListItem _c = new ChannelListItem();
            _c.externalIp = this.target_ip;
            _c.port = this.target_port;
            _c.topic = "";
            _c.name = this.room_name;
            this.hashlink = "\\\\arlnk://" + Ares.Protocols.Hashlinks.EncodeHashlink(_c);
        }

        public void Disconnect()
        {
            try
            {
                this.socket.Disconnect(false);
            }
            catch { }

            this.socket = null;
            this.Connected = false;
            this.linked_users.Clear();
        }

        public void PartAndDisconnect()
        {
            if (this.linked_users.Count > 0)
            {
                foreach (LinkedUserObject l in this.linked_users)
                {
                    if (l.visible)
                        UserPool.Broadcast(0, ServerOutboundPackets.LinkPartPacket(l.name));
                    
                    SCASPool.Broadcast(SCASOutboundPackets.LinkPartedItem(l.name));
                }                
            }

            try
            {
                this.socket.Disconnect(false);
            }
            catch { }

            this.socket = null;
            this.Connected = false;
            this.linked_users.Clear();
        }

        public void SendAndDisconnect()
        {
            this.DispatchOutboundPackets();

            try
            {
                this.socket.Disconnect(false);
            }
            catch { }

            this.socket = null;
            this.Connected = false;
            this.linked_users.Clear();
        }

        public void SendOutboundPacket(byte[] data) // add packet to outbound buffer
        {
            this.outbound_buffer.Enqueue(data);
        }

        public void AddReceivedBytes(byte[] data) // add to received data dump
        {
            this.inbound_buffer.AddRange(data);
        }

        public void RemoveReceivedBytes(int count) // trim received data dump
        {
            this.inbound_buffer.RemoveRange(0, count);
        }

        public byte[] GetReceivedBytes() // retrieve received data dump
        {
            if (this.inbound_buffer.Count > 3)
            {
                return this.inbound_buffer.ToArray();
            }

            return null;
        }

        public void DispatchOutboundPackets() // send all pending packets
        {
            while (this.outbound_buffer.Count > 0)
            {
                try
                {
                    this.socket.Send(this.outbound_buffer.Peek());
                    Stats.BytesSent += (ulong)this.outbound_buffer.Dequeue().Length;
                }
                catch { return; }
            }
        }

        public bool AddNewLinkedUser(LinkedUserObject lUser)
        {
            if (this.linked_users.Count == 0)
            {
                this.linked_users.Add(lUser);
            }
            else // check duplicate
            {
                foreach (LinkedUserObject l in this.linked_users)
                    if (l.name == lUser.name)
                        return false;

                this.linked_users.Add(lUser);
            }

            return true;
        }

        public String RemoveLinkedUser(String real_name)
        {
            if (this.linked_users.Count == 0) return null;

            for (int i = 0; i < this.linked_users.Count; i++)
            {
                if (this.linked_users[i].name == real_name)
                {
                    String result = this.linked_users[i].name;
                    this.linked_users.RemoveAt(i);
                    return result;
                }
            }

            return null;
        }

        public ushort LinkCount()
        {
            ushort x = 0;

            foreach (LinkedUserObject l in this.linked_users)
                if (l.visible)
                    x++;

            return x;
        }

        public LinkedUserObject[] GetLinkedUsers()
        {
            if (this.linked_users.Count == 0)
                return null;

            return this.linked_users.ToArray();
        }

        public LinkedUserObject GetUser(String name)
        {
            foreach (LinkedUserObject l in this.linked_users)
                if (l.name == name)
                    return l;

            return null;
        }

        private LinkedUserObject GetUserStartsWith(String name)
        {
            foreach (LinkedUserObject l in this.linked_users)
                if (l.name.StartsWith(name))
                    return l;

            return null;
        }

        public LinkedCommandObject GetUserByCommandEval(String cmdText)
        {
            LinkedCommandObject cmd = new LinkedCommandObject();
            int first_space = cmdText.IndexOf(" ");

            if (first_space == -1) return cmd;

            cmdText = cmdText.Substring(first_space + 1); // removed command name

            if (cmdText.Length == 0) return cmd;

            if (cmdText.Substring(0, 1) == "\"") // looking for name in quotes method
            {
                cmdText = cmdText.Substring(1);
                int second_quote = cmdText.IndexOf("\"");

                if (second_quote <= 0) return cmd;
                if (cmdText.Substring(0, second_quote).Length == 0) return cmd;

                cmd.lUser = this.GetUserStartsWith(cmdText.Substring(0, second_quote));
                cmdText = cmdText.Substring(second_quote + 1);
                
                if (cmdText.Length >= 2)
                    cmd.args = cmdText.Substring(1);
                else
                    cmd.args = String.Empty;
            }

            return cmd;
        }

    }
}
